package tools;

import graphics.ProgressFrame;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Observable;
import user.Statistic;

/**
 * Download Manager. Credits to <a href="http://java-tips.org"
 * title="Java-Tips.org">www.java-tips.org</a>
 *
 * @author Mattia Zago
 */
public class Downloader extends Observable implements Runnable {

    /**
     * Max dimension of downloading buffer
     */
    private static final int MAX_BUFFER_SIZE = 1024;
    /**
     * ProgressFrame
     */
    private ProgressFrame rsc;
    /**
     * TimeManager
     */
    private TimeManager time;
    /**
     * Thread del download
     */
    private Thread thread;
    /**
     * Status code -> Status Text
     */
    public static final String STATUSES[] = {"Downloading", "Paused", "Complete", "Cancelled", "Error", "Warning"};
    /**
     * Status Code: DOWNLOAD
     */
    public static final int DOWNLOADING = 0;
    /**
     * Status Code: PAUSE
     */
    public static final int PAUSED = 1;
    /**
     * Status Code: COMPLETE
     */
    public static final int COMPLETE = 2;
    /**
     * Status Code: CANCELLED
     */
    public static final int CANCELLED = 3;
    /**
     * Status Code: ERROR
     */
    public static final int ERROR = 4;
    /**
     * Status Code: WARNING
     */
    public static final int WARNING = 5;
    /**
     * Firefox URL:
     * http://amunaptra.altervista.org/Firefox.zip
     */
    public static final String FIREFOX_URL = "http://amunaptra.altervista.org/Firefox.zip";
    /**
     * Chromium URL: http://amunaptra.altervista.org/Chromium.zip
     */
    public static final String CHROMIUM_URL = "http://amunaptra.altervista.org/Chromium.zip";
    /**
     * Urban Terror URL: http://clanurt.es/descargas/UrbanTerror411.zip
     */
    public static final String URBANTERROR_URL = "http://clanurt.es/descargas/UrbanTerror411.zip";
    /**
     * Location of the downloaded file
     */
    public static final String WIN_PATH = "C:\\Users\\" + System.getenv("username") + "\\Downloads\\AmunaptraDownload\\";
    /**
     * Location of the downloaded file
     */
    public static final String UNI_PATH = "/var/tmp/";
    /**
     * Download URL
     */
    private URL url;
    /**
     * Size of download in bytes
     */
    private int size;
    /**
     * Number of bytes downloaded
     */
    private int downloaded;
    /**
     * Current status
     */
    private int status;
    /**
     * Path+Filename of downloadFile
     */
    private String downloadedFilePath;

    /**
     * Costructor
     *
     * @param url URL object
     * @param rsc Window that show the progress of download
     */
    public Downloader(URL url, ProgressFrame rsc) {
        this.rsc = rsc;
        this.url = url;
        size = -1;
        downloaded = 0;
        status = DOWNLOADING;
        time = new TimeManager(rsc);
    }

    /**
     * Start Download
     */
    public void start() {
        this.rsc.setController(this);
        // Begin the download.
        download();
    }

    /**
     * @return URL to string
     */
    public String getUrl() {
        return url.toString();
    }

    /**
     * @return the dimension of file that pointed by URL
     */
    public int getSize() {
        return size;
    }

    /**
     * @return Progress value
     */
    public int getProgress() {
        return ((int) downloaded / size) * 100;
    }

    /**
     * Convert bytes in human readable format<br> Thanks to <a
     * href="http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java">http://stackoverflow.com</a>
     *
     * @param bytes bytes
     * @param si Use 1000b = 1MB instead of 1024b = 1MB?
     * @return String
     */
    public static String humanReadableByteCount(long bytes, boolean si) {
        int unit = si ? 1000 : 1024;
        if (bytes < unit) {
            return bytes + " B";
        }
        int exp = (int) (Math.log(bytes) / Math.log(unit));
        String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
        return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
    }

    /**
     * @return Code of current status
     */
    public int getStatus() {
        return status;
    }

    /**
     * @return Path/Filename of the downloaded file
     */
    public String getDownloadedFilePath() {
        return this.downloadedFilePath;
    }

    /**
     * Pause downloading.
     *
     * @see Downloader.resume() for resume
     */
    public void pause() {
        status = PAUSED;
        stateChanged();
    }

    /**
     * Resume download
     */
    public void resume() {
        status = DOWNLOADING;
        stateChanged();
        download();
    }

    /**
     * Stop download
     */
    public void cancel() {
        status = CANCELLED;
        stateChanged();
    }

    /**
     * Custom error message
     *
     * @param text Label the indicates error
     * @see MessageManager.downloadError
     */
    private void error(String text) {
        status = ERROR;
        stateChanged();
        MessageManager.downloadError(rsc, text);
    }

    /**
     * Custom warning message
     *
     * @param text Label for warning
     * @see MessageManager.generic
     */
    private void warning(String text) {
        status = WARNING;
        stateChanged();
        MessageManager.warning(rsc, text, "Warning in Download");
    }

    /**
     * Start or resume download
     */
    private void download() {
        thread = new Thread(this);
        thread.setName("Download Thread");
        thread.start();
    }

    /**
     * @param url URL that point to the file
     * @return Filename to string
     */
    private String getFileName(URL url) {
        String fileName = url.getFile();
        return fileName.substring(fileName.lastIndexOf('/') + 1);
    }

    /**
     * Run
     */
    @Override
    public void run() {
        long timeStartForLog = System.currentTimeMillis();
        RandomAccessFile file = null;
        InputStream stream = null;
        rsc.setVisible(true);
        rsc.ProgressBar.setIndeterminate(true);
        rsc.ProgressBar.setStringPainted(false);
        try {
            time.start();
            rsc.Action.setText("Apertura connessione...");
            //rsc.ProgressBar.setString("Attempt to connect to: http://amunaptra.zagomattia.it/");
            // Open connection to URL.
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            // Specify what portion of file to download.
            connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
            rsc.Action.setText("Connessione a www.amunaptra.zagomattia.it");
            // Connect to server.
            connection.connect();
            rsc.Action.setText("Apertura stream...");
            stream = connection.getInputStream();

            // Check for valid content length.
            int contentLength = connection.getContentLength();
            if (contentLength < 1) {
                error("Lunghezza < 1");
            }

            /* Set the size for this download if it
             hasn't been already set. */
            if (size == -1) {
                size = contentLength;
                stateChanged();
            }
            rsc.ProgressBar.setMaximum(size);

            rsc.Action.setText("Apertura file..");
            // Open file and seek to the end of it.
            String path = UNI_PATH;
            if (System.getProperty("os.name").toLowerCase().contains("win")) {
                path = WIN_PATH;
            }
            new File(path).mkdir();
            this.downloadedFilePath = path + getFileName(url);
            File archive = new File(downloadedFilePath);
            System.out.println("======================\nPATH: "+archive.getAbsolutePath()+"\n======================");
            if(archive.exists()) {
                // già scaricato
                MessageManager.info(rsc, "Hai già scaricato questo archivio. Procedo all'estrazione", "Archivio già scaricato");
                status = COMPLETE;
                stateChanged();
                archive = null;
            } else {
                file = new RandomAccessFile(this.downloadedFilePath, "rw");
                file.seek(downloaded);
            }

            rsc.ProgressBar.setIndeterminate(false);
            rsc.ProgressBar.setStringPainted(true);
            System.out.println("======================\nSTART DOWNLOAD\n======================");
            while (status == DOWNLOADING) {
                /* Size buffer according to how much of the
                 file is left to download. */
                byte buffer[];
                if (size - downloaded > MAX_BUFFER_SIZE) {
                    buffer = new byte[MAX_BUFFER_SIZE];
                } else {
                    buffer = new byte[size - downloaded];
                }
                System.out.println(time.timeFromStart() + "\t" + "Download " + humanReadableByteCount(downloaded, false) + " of " + humanReadableByteCount(size, false) + " byte");
                rsc.Action.setText("Download " + humanReadableByteCount(downloaded, false) + " of " + humanReadableByteCount(size, false) + " byte");


                // Read from server into buffer.
                int read = stream.read(buffer);
                if (read == -1) {
                    break;
                }

                // Write buffer to file.
                file.write(buffer, 0, read);
                downloaded += read;
                rsc.ProgressBar.setValue(downloaded);
                //rsc.ProgressBar.setString((int) (rsc.ProgressBar.getValue() / rsc.ProgressBar.getMaximum())*100 + "%");
                stateChanged();
            }

            time.stop();
            System.out.println("======================\nEND DOWNLOAD\n======================");
            /* Change status to complete if this point was
             reached because downloading has finished. */
            if (status == DOWNLOADING) {
                status = COMPLETE;
                stateChanged();
            }
            System.out.println("STATUS: " + STATUSES[status] + "\n======================");
            if (status == COMPLETE) {
                extract();
            } 
        } catch (FileNotFoundException e) {
            error("Non trovo l'archivio in: "+e.getMessage());
        } catch (Exception e) {
            error(e.getMessage());
        } finally {
            System.out.println();
            System.out.println("======================\nFINALIZATION\n======================");
            // Close file.
            if (file != null) {
                try {
                    file.close();
                    System.out.println("File closed");
                } catch (Exception e) {
                    warning("Errore nella chiusura del file!");
                    MessageManager.logger(e.getMessage());
                }
            }

            // Close connection to server.
            if (stream != null) {
                try {
                    stream.close();
                    System.out.println("Stream closed");
                } catch (Exception e) {
                    warning("Errore nella chiusura dello stream");
                    MessageManager.logger(e.getMessage());
                }
            }
            // Dispose 
            rsc.dispose();
            System.out.println("RSC dispose");
            Statistic.updateStatFile(TimeManager.getTimeToString()+" - "+System.getProperty("user.name")+" finish download in: "+TimeManager.getIntervalToString(timeStartForLog));
            
            // Sblocco i file
            file = null;
            stream = null; 
            try {
                File archive = new File(this.downloadedFilePath);
                System.out.println("Attempt to delete '"+this.downloadedFilePath+"'... "+(archive.delete()?"done":"!!FILE NOT DELETED"));
            } catch(java.lang.NullPointerException E) {    
            }
            
        }
    }

    /**
     * Notify observers that this download's status has changed.
     */
    private void stateChanged() {
        setChanged();
        notifyObservers();
    }

    /**
     * Verify download url
     *
     * @param url String URL
     * @return URL Object
     */
    public static URL verifyUrl(String url) {
        // Only allow HTTP URLs.
        if (!url.toLowerCase().startsWith("http://")) {
            return null;
        }
        // Verify format of URL.
        URL verifiedUrl;
        try {
            verifiedUrl = new URL(url);
        } catch (Exception e) {
            return null;
        }
        // Make sure URL specifies a file.
        if (verifiedUrl.getFile().length() < 2) {
            return null;
        }
        return verifiedUrl;
    }

    private void extract() {
        // Setup della finestra di download
        String name = "";
        // Evito di mettere le prime lettere in modo da evitare il doppio controllo Firefox/firefox
        if (downloadedFilePath.contains("hromium")) {
            name = "Chromium";
        } else if (downloadedFilePath.contains("irefox")) {
            name = "Firefox";
        }
        System.out.println("Config Extractor Object with path: "+downloadedFilePath);
        rsc.setUp(new javax.swing.ImageIcon(getClass().getResource("/graphics/image/" + name + ".png")), "Estrazione di " + name, "Sii paziente :)");
        Extractor ex = new Extractor(rsc, downloadedFilePath);
        ex.extract();
    }
}